package com.lanxin.generator.service;

import com.lanxin.generator.anno.PugDoc;
import com.lanxin.generator.util.*;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.*;

import java.awt.*;
import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.*;
import java.util.List;

/**
 * @Author: 董伟豪
 * @Date: 2023/1/13 16:56
 */
@Slf4j
public class GeneratorService {

    /**
     * 中文字符排序，根据a,b,c,d,e
     */
    private Comparator comparator = Collator.getInstance(Locale.CHINA);

    /**
     * 全局设置
     */
    PugGlobalConfig pugGlobalConfig = PugGlobalConfig.builder()
            .isoverride()
            .author("蓝欣")
            .title("轮播图")
            .gitlink("前台代码 git clone https://gitee.com/kekesam/kuangstudy-pug-ui.git")
            .url("后台代码 git clone https://gitee.com/kekesam/kuangstudy-pug-parent.git")
            .commentDate("yyyy-MM-dd HH:mm:ss")
            .version("1.0.0")
            .port("8877")
            .apititle("蓝欣在线接口文档")
            .apidesc("蓝欣在线接口文档我是一个描述")
            .apimemebers("蓝欣")
            .apiversion("1.0.0")
            .apiurl("http://localhost:8877/admin")
            .build();

    /**
     * 目前是能拿到全部的类，那么能拿到类的话，接下来就获取类上注解的信息和参数返回值之类的东西
     * TestController = {
     *      title: "用户管理",
     *      methods: [
     *          {method: "saveUser",type: "Post",path: "/admin/user/save",parameters: [{}],returnType: {}},
     *          {method: "updateUser",type: "Post",path: "/admin/user/save",parameters: [{}],returnType: {}},
     *      ]
     * }
     * @param controller controller层所在的路径
     * @param outPath 输出地址
     * @param filename 文件名字
     */
    public void createApi(String controller, String outPath, String filename) {
        try {
            // 获取所有的类
            Set<Class> classes = ClassScaner.scan(controller, RestController.class);
            List<Map<String, Object>> objectList = new ArrayList<>();
            // 把包下所有的类进行遍历
            for (Class aClass : classes) {
                TreeMap<String, Object> apiList = createApiDoc(aClass);
                objectList.add(apiList);
            }
            // 创建在线文档
            create(objectList, pugGlobalConfig, outPath, filename);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 初始化数据源
     */
    PugDataSourceConfig dataSourceConfig = PugDataSourceConfig.builder()
            .url("jdbc:mysql://127.0.0.1:3306/kss-zixun-db?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf-8&useSSL=false")
            .username("root")
            .password("dp899556677").build();

    /**
     * 获取每个类的所有的方法，方法对应的参数，返回值，注解等信息，获取就像TestController中的东西
     * @param aClass 类  这里一定要将Class进行泛型，我在获取Annotation的时候就是Annotation对象，而Annotation无法获取注解信息
     * @return
     */
    private TreeMap<String, Object> createApiDoc(Class<?> aClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        // 创建一个map容器，去装一个class对象的方法信息(TreeMap可以保证键的排序和唯一性，并且还能排序,按照指定的comparator排序)
        TreeMap<String, Object> treeMap = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return comparator.compare(o1, o2);
            }
        });
        PugDoc myPugDoc = aClass.getAnnotation(PugDoc.class);
        // 全限定类名
        treeMap.put("name",aClass.getName());
        // 普通类名
        treeMap.put("sname",aClass.getSimpleName());
        if(myPugDoc != null){
            treeMap.put("title",myPugDoc.title());
            treeMap.put("tableName",myPugDoc.tableName());
            treeMap.put("model",myPugDoc.model());
            treeMap.put("desc",myPugDoc.desc());
        }else{
            // 如果我没有加上这个注解的话，默认为当前类的名字
            treeMap.put("title",aClass.getSimpleName());
            treeMap.put("tableName","");
            treeMap.put("model","");
            treeMap.put("desc","");
        }

        // 循环类的所有方法
        ArrayList<TreeMap<String, Object>> mapList = new ArrayList<>();
        // 与aClass.getMethods是获取所有公共方法，包括继承自父类的，这样明显和业务需求一样
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for(Method method : declaredMethods){
            // 放置方法的参数
            TreeMap<String, Object> objectTreeMap = new TreeMap<>(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return comparator.compare(o1, o2);
                }
            });
            objectTreeMap.put("methodName",method.getName());
            objectTreeMap.put("returnName",method.getReturnType().getName());
            objectTreeMap.put("returnSName",method.getReturnType().getSimpleName());

            // 返回值有基础数据类型，封装数据类型，类类型（bean,List,Map Page）
            if (method.getReturnType() == Integer.class
                    || method.getReturnType() == Long.class
                    || method.getReturnType() == byte.class
                    || method.getReturnType() == short.class
                    || method.getReturnType() == int.class
                    || method.getReturnType() == long.class
                    || method.getReturnType() == float.class
                    || method.getReturnType() == double.class
                    || method.getReturnType() == char.class
                    || method.getReturnType() == Byte.class
                    || method.getReturnType() == Short.class
                    || method.getReturnType() == Float.class
                    || method.getReturnType() == Double.class
                    || method.getReturnType() == Long[].class
                    || method.getReturnType() == Integer[].class
                    || method.getReturnType() == Byte[].class
                    || method.getReturnType() == Double[].class
                    || method.getReturnType() == Short[].class
            ){
                // 返回数字类型
                // 这里如果返回JSON在线上会增加json体积和大小，因为格式化会增加换行符，这里看后续修复了
                objectTreeMap.put("returnList", JsonUtil.obj2StringPretty(R.success("1")));
            }else if(method.getReturnType() == String.class || method.getReturnType() == String[].class){
                // 因为springmvc数据转换器对String是有特殊处理 StringHttpMessageConverter
                objectTreeMap.put("returnList", JsonUtil.obj2StringPretty(R.success("success")));
            }else if(method.getReturnType() == boolean.class || method.getReturnType() == boolean[].class ||
                            method.getReturnType() == Boolean.class || method.getReturnType() == Boolean[].class){
                objectTreeMap.put("returnList", JsonUtil.obj2StringPretty(R.success("true")));
            }else{
                // 这里都是类类型
                // 返回的是泛型里面的类型 ---------> List<Bo> 这里我需要的是Bo里面的属性
                Type genericReturnType = method.getGenericReturnType();
                // 泛型里面的对象
                Type tactuallyTypeArgument = null;
                // 返回的类型
                Type ownerType = null;
                // 泛型类型
                if (genericReturnType instanceof ParameterizedType) {
                    ownerType = ((ParameterizedType) genericReturnType).getRawType();
                    Type[] actuallyTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                    for (Type actuallyTypeArgument : actuallyTypeArguments) {
                        tactuallyTypeArgument = actuallyTypeArgument;
                    }
                } else {
                    // Bo
                    tactuallyTypeArgument = genericReturnType;
                    ownerType = genericReturnType;
                }

                if(tactuallyTypeArgument != null){
                    // 这里应该是需要实例化的，不实例化的化无法通过jackJson返回出去，得到泛型的具体类型在实例化
                    Class<?>[] object = Class.forName(tactuallyTypeArgument.getTypeName()).getInterfaces();
                    // 这里我下面有很多if中会出现
                    ArrayList<Object> list = new ArrayList<>();
                    // List<Bo>
                    if(ownerType == List.class){
                        list.add(object);
                        objectTreeMap.put("returnList",JsonUtil.obj2StringPretty(R.success(list)));
//                    }else if(ownerType == IPage.class){
//                        list.add(object);
//                        IPage<Object> page = new Page<>(1, 10);
//                        page.setRecords(list);
//                        objectTreeMap.put("returnList",JsonUtil.obj2StringPretty(R.success(page)));
                    }else{
                        // Bo的返回
                        objectTreeMap.put("returnList",JsonUtil.obj2StringPretty(R.success(object)));
                    }
                }else{
                    // void的返回
                    objectTreeMap.put("returnList",JsonUtil.obj2StringPretty(R.success("")));
                }
            }


            // 参数部分
            Parameter[] parameters = method.getParameters();
            List<Map> parameterTypeMap = new ArrayList<>();
            for(Parameter parameter : parameters){
                Class<?> parameterType = parameter.getType();
                // 开始判断类型是不是基础数据类型和String
                if (parameterType == String.class || parameterType == Integer.class
                        || parameterType == Long.class
                        || parameterType == Byte.class
                        || parameterType == Short.class
                        || parameterType == Float.class
                        || parameterType == Double.class
                        || parameterType == Boolean.class
                        || parameterType == String[].class
                        || parameterType == Long[].class
                        || parameterType == Integer[].class
                        || parameterType == Byte[].class
                        || parameterType == Double[].class
                        || parameterType == Short[].class
                        || parameterType == Boolean[].class
                ) {
                    HashMap<String, Object> stringObjectHashMap = new HashMap<>();
                    // java.lang.String
                    stringObjectHashMap.put("parameterName", parameter.getName());
                    // String
                    stringObjectHashMap.put("parameterSName", parameterType.getSimpleName());
                    List<Map<String, Object>> fieldMaps = new ArrayList<>();
                    PathVariable pathVariableAnnotation = parameter.getAnnotation(PathVariable.class);
                    RequestParam requestParamAnnotation = parameter.getAnnotation(RequestParam.class);
                    RequestBody requestBodyAnnotation = parameter.getAnnotation(RequestBody.class);
                    Boolean required = false;
                    String defaultV = "";
                    String annotation = "";
                    String title = "";
                    if(pathVariableAnnotation != null){
                        required = true;
                        annotation = "@PathVariable";
                        title = pathVariableAnnotation.value();
                    }
                    if(requestParamAnnotation != null && requestParamAnnotation.required()){
                        required = true;
                    }
                    if(requestParamAnnotation != null){
                        defaultV = requestParamAnnotation.defaultValue();
                        annotation = "@RequestParam";
                        title = requestParamAnnotation.value();
                    }
                    HashMap<String, Object> childrenStringObjectHashMap = new HashMap<>();
                    childrenStringObjectHashMap.put("name", title);
                    childrenStringObjectHashMap.put("title", title);
                    childrenStringObjectHashMap.put("desc", "");
                    childrenStringObjectHashMap.put("default", defaultV);
                    childrenStringObjectHashMap.put("required", required ? "Y" : "N");
                    childrenStringObjectHashMap.put("type", parameterType.getTypeName());
                    childrenStringObjectHashMap.put("stype", parameterType.getTypeName().replaceAll("java\\.lang\\.", ""));
                    childrenStringObjectHashMap.put("annotation", annotation);
                }
            }

            // 解析的方法放入到方法Map中
            objectTreeMap.put("parameterList", parameterTypeMap);
            // method方法的注解部分
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
            PutMapping putMapping = method.getAnnotation(PutMapping.class);
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);

            // 是不是文档注解
            if (myPugDoc != null) {
                treeMap.put("title", myPugDoc.title());
                treeMap.put("desc", myPugDoc.desc());
                treeMap.put("token", myPugDoc.token());
            } else {
                treeMap.put("title", method.getName());
                treeMap.put("desc", method.getName());
            }

            // 注解是不是请求注解
            if (postMapping != null) {
                treeMap.put("httpType", "POST");
                treeMap.put("httpTypes", "POST".toLowerCase());
                treeMap.put("url", postMapping.value()[0]);
            } else if (getMapping != null) {
                treeMap.put("httpType", "GET");
                treeMap.put("httpTypes", "GET".toLowerCase());
                treeMap.put("url", getMapping.value()[0]);
            } else if (deleteMapping != null) {
                treeMap.put("httpType", "DELETE");
                treeMap.put("httpTypes", "DELETE".toLowerCase());
                treeMap.put("url", deleteMapping.value()[0]);
            } else if (putMapping != null) {
                treeMap.put("httpType", "PUT");
                treeMap.put("httpTypes", "PUT".toLowerCase());
                treeMap.put("url", putMapping.value()[0]);
            }else{
                treeMap.put("httpType", "ALL");
                treeMap.put("httpTypes", "ALL".toLowerCase());
                treeMap.put("url", requestMapping.value()[0]);
            }
            mapList.add(treeMap);
        }


        // 获取数据表信息
        String tableName = String.valueOf(treeMap.get("tableName"));
        if (StringUtils.isNotEmpty(tableName)) {
            PugDataTableSourceConfig pugDataTableSourceConfig = PugDataTableSourceConfig.builder()
                    .tablename(tableName).buildTableInfo(dataSourceConfig);
            treeMap.put("fields", pugDataTableSourceConfig.getTableInfos());
        } else {
            treeMap.put("fields", new ArrayList<>());
        }

        treeMap.put("methodInfos", mapList);

        return treeMap;
    }


    public void create(List<Map<String, Object>> mapList,
                       PugGlobalConfig pugGlobalConfig, String outPath, String fileName) {
        try {
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
 /*
            // 这个是一个错误的写法，无法读取jar里面的template
            File file = new ClassPathResource("/templates").getFile();
            cfg.setDirectoryForTemplateLoading(file);
*/
            // 指定模板文件从何处加载的数据源，这里设置成一个文件目录
            cfg.setTemplateLoader(new ClassTemplateLoader(
                    this.getClass().getClassLoader(),  "/templates"));
            cfg.setDefaultEncoding("utf-8");



            // 指定模板如何检索数据模型，这是一个高级的主题了… // 但先可以这么来用：
            cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_28));
            // 这里创建的值都可以在页面模板中取出来
            // 创建根哈希表
            Map root = new HashMap();
            // 在根中放入字符串"user"
            root.put("author", pugGlobalConfig.getAuthor());
            root.put("datetime", pugGlobalConfig.getDatetime());
            root.put("title", pugGlobalConfig.getTitle());
            root.put("burl", pugGlobalConfig.getUrl());
            root.put("version", pugGlobalConfig.getVersion());
            root.put("bootversion", pugGlobalConfig.getBootversion());
            root.put("gitlink", pugGlobalConfig.getGitlink());
            root.put("apititle", pugGlobalConfig.getApititle());
            root.put("apidesc", pugGlobalConfig.getApidesc());
            root.put("apiurl", pugGlobalConfig.getApiurl());
            root.put("apiversion", pugGlobalConfig.getApiversion());
            root.put("apimemebers", pugGlobalConfig.getApimemebers());
            root.put("rootPath", "com.lanxin.generator");


            root.put("mapList", mapList);


            Template temp = cfg.getTemplate("api/template.html");
            // 指定最终渲染的页面存储的位置
            // System.getProperty("user.dir")
            File targetFile = new File(outPath, fileName);

            // 这里有无法触发，不知道是什么原因，但是我打断点一步步走就可以
            Runtime ce = Runtime.getRuntime();
            ce.exec("cmd /c start " + targetFile.getPath());

            log.info("文档创建成功，位置为：" + targetFile.getPath());
            // System.out.println("文档创建成功，位置为：" + targetFile.getPath());
            Writer out = new OutputStreamWriter(new FileOutputStream(targetFile), StandardCharsets.UTF_8);
            // freemaker的模板渲染替换
            temp.process(root, out);
            out.flush();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
